﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using BEELab.Data;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using System.Web.UI.WebControls;

namespace BEELab.Logic.ProgramManagement.StageManagement
{
    public static class StageManagement
    {
        public static Dictionary<string, string> ImportStageIntoStage(int FromStageID, int ToStageID, int ContainerID, string NamePrefix, bool MaintainContainers, bool KeepVariables, Dictionary<int,int> VariablesDictionary)
        {
            Dictionary<string, string> result = new Dictionary<string, string>();

            SqlCommand command = new SqlCommand();

            int FromOnLoadBlockID = BlockElements.GetBlockIDByName(FromStageID, Properties.Settings.Default.OnLoadBlockName);
            int FromOnPostBlockID = BlockElements.GetBlockIDByName(FromStageID, Properties.Settings.Default.OnPostBlockName);
            int FromOnMessageBlockID = BlockElements.GetBlockIDByName(FromStageID, Properties.Settings.Default.OnMessageBlockName);
            int FromBeforeLoadBlockID = BlockElements.GetBlockIDByName(FromStageID, Properties.Settings.Default.BeforeLoadBlockName);
            int FromAfterPostBlockID = BlockElements.GetBlockIDByName(FromStageID, Properties.Settings.Default.AfterPostBlockName);

            int ToOnLoadBlockID = BlockElements.GetBlockIDByName(ToStageID, Properties.Settings.Default.OnLoadBlockName);
            int ToOnPostBlockID = BlockElements.GetBlockIDByName(ToStageID, Properties.Settings.Default.OnPostBlockName);
            int ToOnMessageBlockID = BlockElements.GetBlockIDByName(ToStageID, Properties.Settings.Default.OnMessageBlockName);
            int ToBeforeLoadBlockID = BlockElements.GetBlockIDByName(ToStageID, Properties.Settings.Default.BeforeLoadBlockName);
            int ToAfterPostBlockID = BlockElements.GetBlockIDByName(ToStageID, Properties.Settings.Default.AfterPostBlockName);

            Dictionary<int, int> newIDs = new Dictionary<int, int>();
            newIDs.Add(FromOnLoadBlockID, ToOnLoadBlockID);
            newIDs.Add(FromOnMessageBlockID, ToOnMessageBlockID);
            newIDs.Add(FromOnPostBlockID, ToOnPostBlockID);
            if (FromBeforeLoadBlockID != 0)
            {
                newIDs.Add(FromBeforeLoadBlockID, ToBeforeLoadBlockID);
            }
            if (FromAfterPostBlockID != 0)
            {
                newIDs.Add(FromAfterPostBlockID, ToAfterPostBlockID);
            }

            foreach (int id in BlockElements.ListByStageID(FromStageID))
            {
                if (id == FromOnLoadBlockID || id == FromOnMessageBlockID || id == FromOnPostBlockID || id == FromBeforeLoadBlockID || id == FromAfterPostBlockID)
                    continue;

                newIDs.Add(id, BlockElements.ImportElement(id, ToStageID, NamePrefix));

                if (ContainerID != 0)
                {
                    string name = BlockElements.GetNameByID(id);
                    result[name] = NamePrefix + name;
                    ContainerManagement.AddElement(ContainerID, "BlockElement", newIDs[id], name);
                }
            }

            newIDs[0] = 0;
            foreach (int id in newIDs.Keys)
            {
                command = new SqlCommand();
                command.CommandText = "UPDATE BlockElements SET ParentID=@NewParentID WHERE ParentID=@OldParentID AND StageID=@NewStageID";
                command.Parameters.Add(new SqlParameter("@NewParentID", newIDs[id]));
                command.Parameters.Add(new SqlParameter("@OldParentID", id));
                command.Parameters.Add(new SqlParameter("@NewStageID", ToStageID));
                DBHelper.ExecuteNonQuery(command);
            }

            // handle containers
            if (MaintainContainers)
            {
                foreach (int id in ContainerManagement.ListByStageID(FromStageID))
                {
                    ContainerManagement.DuplicateContainer(ToStageID, id, newIDs, KeepVariables, VariablesDictionary);
                }
            }

            int newID;
            foreach (int id in PresentationElements.ListByStageID(FromStageID))
            {
                newID = PresentationElements.ImportElement(id, ToStageID, NamePrefix);
                string name = PresentationElements.GetNameByID(id);
                if (ContainerID != 0)
                {
                    result[name] = NamePrefix + name;
                    ContainerManagement.AddElement(ContainerID, "PresentationElement", newID, name);
                }

                if (name == Properties.Settings.Default.PresentationLayer + FromStageID)
                {
                    result[name] = NamePrefix + Properties.Settings.Default.PresentationLayer + ToStageID;
                    PresentationElements.SetName(newID, result[name]);
                }
            }

            foreach (int id in DataElements.ListByStageID(FromStageID))
            {
                newID = DataElements.ImportElement(id, ToStageID, NamePrefix);
            }

            return new Dictionary<string, string>(result);
        }

        /// <summary>
        /// Creates new stage to the parent program 
        /// </summary>
        /// <param name="ProgramID">Parent Program ID</param>
        /// <param name="Name">Stage Name</param>
        /// <param name="Description">Stage Description</param>
        /// <param name="CreateSystemDefaults">Boolean value - if True,
        /// will create two empty blocks that will executed once stage is loaded</param>
        /// <param name="OnMessage">Boolean value - if True,
        /// will create an empty blocks that will be executed when a message arrives</param>
        /// <param name="BeforeAndAfter">Boolean value - if True,
        /// will create two empty blocks that will executed on the server before this stage is loaded</param>
        /// <returns>Number of rows effected on DB</returns>
        public static int CreateStage(int ProgramID, string Name, string Description, Boolean CreateSystemDefaults, Boolean OnMessage, Boolean BeforeAndAfter)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "INSERT INTO Stages (ProgramID, Name, Description) VALUES (@ProgramID, @Name, @Description); SELECT SCOPE_IDENTITY();";
            command.Parameters.Add(new SqlParameter("@ProgramID", ProgramID));
            command.Parameters.Add(new SqlParameter("@Name", Name));
            command.Parameters.Add(new SqlParameter("@Description", Description));

            int ID = int.Parse(DBHelper.ExecuteScalar(command));

            if (CreateSystemDefaults)
            {
                ProgramManagement.StageManagement.BlockElements.CreateElement("Block", ID, 0, 0, Properties.Settings.Default.OnLoadBlockName, "This is a System block which is executed when this stage is loaded", "", true);
                ProgramManagement.StageManagement.BlockElements.CreateElement("Block", ID, 0, 0, Properties.Settings.Default.OnPostBlockName, "This is a System block which is executed when this stage is posted", "", true);
            }

            if (BeforeAndAfter)
            {
                ProgramManagement.StageManagement.BlockElements.CreateElement("Block", ID, 0, 0, Properties.Settings.Default.BeforeLoadBlockName, "This is a System block which is executed on the server before this stage is loaded", "", true);
                ProgramManagement.StageManagement.BlockElements.CreateElement("Block", ID, 0, 0, Properties.Settings.Default.AfterPostBlockName, "This is a System block which is executed on the server after this stage is posted", "", true);
            }

            if (OnMessage)
            {
                ProgramManagement.StageManagement.BlockElements.CreateElement("Block", ID, 0, 0, Properties.Settings.Default.OnMessageBlockName, "This is a System block which is executed when a message arrives", "", true);
            }

            return ID;
        }

        /// <summary>
        /// Set new default stage to the experiment. If there is on stages, place NULL as first stage.
        /// </summary>
        /// <param name="progID">Program ID</param>
        /// <returns>Name of new first stage</returns>
        public static String setDefaultFirstStage(String progID)
        {
            String firstStage = null;
            int programID = Convert.ToInt32(progID);
            List<String> stages = BEELab.Logic.ProgramManagement.StageManagement.StageManagement.GetStagesList(progID);
            if (stages != null && stages.Count() > 0)
            {
                firstStage = stages[0];
                SetFirstStage(programID, firstStage);
            }
            else
                DeleteFirstStage(progID);
            return firstStage;
        }

        /// <summary>
        /// Delete stage from an experiment stages list. 
        /// If this stage was the first stage of the experiment, will set a new default stage.
        /// </summary>
        /// <param name="stageID">Stage ID</param>
        /// <param name="progID">Program ID</param>
        /// <returns>True if secceeded, False otherwise</returns>
        public static Boolean DeleteStage(int stageID, String progID)
        {
            //Saves name of deletes stage in order to check if it was first stage
            String query = "SELECT Name FROM Stages WHERE ID=@stageID";
            SqlCommand command = new SqlCommand();
            command.CommandText = query;
            command.Parameters.Add(new SqlParameter("@stageID", stageID));
            String stageName = DBHelper.ExecuteScalar(command);

            //Delete stage
            query = "DELETE FROM Variables WHERE ID IN (SELECT ElementID FROM ContainerElements WHERE Type='Variable' AND ContainerID IN (SELECT ID FROM Containers WHERE StageID=@stageID)); DELETE FROM Stages WHERE ID=@stageID";
            command = new SqlCommand();
            command.CommandText = query;
            command.Parameters.Add(new SqlParameter("@stageID", stageID));
            int res = DBHelper.ExecuteNonQuery(command);

            //If stage was deleted successfully
            if (res > 0)
            {
                //If it was the first stage - set new default one 
                if (GetFirstStage(progID).Equals(stageName))
                    setDefaultFirstStage(progID.ToString());
                return true;
            }

            return false;
        }

        /// <summary>
        /// Updates stage name and description on DB
        /// </summary>
        /// <param name="ID">Stage ID</param>
        /// <param name="Name">Stage name</param>
        /// <param name="Description">Stage description</param>
        /// <returns>True if succeeded, False otherwise</returns>
        public static Boolean UpdateStage(int ID, string Name, string Description)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "UPDATE Stages SET Name=@Name, Description=@Description WHERE ID=@ID";
            command.Parameters.Add(new SqlParameter("@Name", Name));
            command.Parameters.Add(new SqlParameter("@Description", Description));
            command.Parameters.Add(new SqlParameter("@ID", ID));

            int res = DBHelper.ExecuteNonQuery(command);

            if (res == 1)
                return true;

            return false;
        }

        /// <summary>
        /// Sets specific stage as the experiment first stage
        /// </summary>
        /// <param name="ProgramID">Program ID</param>
        /// <param name="StageName">Stage Name to become first</param>
        /// <returns>True if succeeded, False otherwise</returns>
        public static Boolean SetFirstStage(int ProgramID, string StageName)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "UPDATE Programs SET FirstStage=@FirstStage WHERE ID=@ID";
            command.Parameters.Add(new SqlParameter("@FirstStage", StageName));
            command.Parameters.Add(new SqlParameter("@ID", ProgramID));

            int res = DBHelper.ExecuteNonQuery(command);

            if (res == 1)
                return true;

            return false;
        }

        /// <summary>
        /// Places NULL value on experiment first stage value.
        /// Occures when there is still no stages at the experiment
        /// </summary>
        /// <param name="ProgramID">Program ID</param>
        public static void DeleteFirstStage(String ProgramID)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "UPDATE Programs SET FirstStage = null WHERE ID=@ProgramID";
            command.Parameters.Add(new SqlParameter("@ProgramID", ProgramID));
            DBHelper.ExecuteScalar(command);
        }

        /// <summary>
        /// Returns first stage of an experiment
        /// </summary>
        /// <param name="ProgramID">Program ID</param>
        /// <returns>First stage name</returns>
        public static string GetFirstStage(String ProgramID)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "Select FirstStage FROM Programs WHERE ID=@ID";
            command.Parameters.Add(new SqlParameter("@ID", ProgramID));
            return DBHelper.ExecuteScalar(command);
        }

        /// <summary>
        /// Returns a list of all stages names of a specific experiment
        /// </summary>
        /// <param name="ProgramID">Program ID</param>
        /// <returns>List of all stages names</returns>
        public static List<String> GetStagesList(String ProgramID)
        {
            List<String> res = new List<string>();
            SqlCommand command = new SqlCommand();
            command.CommandText = "SELECT Name FROM Stages WHERE ProgramID=@ID";
            command.Parameters.Add(new SqlParameter("@ID", ProgramID));
            DataTable dt = DBHelper.ExecuteQuery(command);
            if (dt != null)
            {
                foreach (DataRow dr in dt.Rows)
                {
                    foreach (DataColumn dc in dt.Columns)
                    {
                        res.Add(dr[dc].ToString());
                    }
                }
                return res;
            }
            return null;
        }

        /// <summary>
        /// Gets program ID and returns all its stages IDs in a list
        /// </summary>
        /// <param name="ID">Program ID</param>
        /// <returns>List of stages IDs</returns>
        public static List<int> ListByProgramID(int ID)
        {
            List<int> result = new List<int>();

            SqlCommand command = new SqlCommand();
            command.CommandText = "SELECT ID FROM Stages WHERE ProgramID=@ProgramID";
            command.Parameters.Add(new SqlParameter("@ProgramID", ID));
            DataTable dt = DBHelper.ExecuteQuery(command);

            foreach (DataRow dr in dt.Rows)
            {
                result.Add((int)dr["ID"]);
            }

            return result;
        }

        /// <summary>
        /// Gets program ID and Stage name ans returns stage ID
        /// </summary>
        /// <param name="ProgramID">Program ID</param>
        /// <param name="Name">Stage Name</param>
        /// <returns>Stage ID</returns>
        public static int GetIDByName(int ProgramID, string Name)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "SELECT ID FROM Stages WHERE ProgramID=@ProgramID AND Name=@Name";
            command.Parameters.Add(new SqlParameter("@ProgramID", ProgramID));
            command.Parameters.Add(new SqlParameter("@Name", Name));

            string res = DBHelper.ExecuteScalar(command);

            if (res == null)
                return 0;

            return int.Parse(res);
        }

        /// <summary>
        /// Gets stage ID and returns its Stage name
        /// </summary>
        /// <param name="ID">Stage ID</param>
        /// <returns>Stage name</returns>
        public static string GetName(int ID)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "Select Name FROM Stages WHERE ID=@ID";
            command.Parameters.Add(new SqlParameter("@ID", ID));
            return DBHelper.ExecuteScalar(command);
        }

        /// <summary>
        /// Gets stage ID and returns its program's ID
        /// </summary>
        /// <param name="ID">Stage ID</param>
        /// <returns>Program ID</returns>
        public static int GetProgramID(int ID)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "Select ProgramID FROM Stages WHERE ID=@ID";
            command.Parameters.Add(new SqlParameter("@ID", ID));
            return int.Parse(DBHelper.ExecuteScalar(command));
        }

        /// <summary>
        /// Updates stage parent experiment
        /// </summary>
        /// <param name="ID">Stage ID</param>
        /// <param name="ProgramID">new Program ID</param>
        /// <returns>The number of rows affected on DB</returns>
        public static Boolean SetProgramID(int ID, int ProgramID)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "UPDATE Stages SET ProgramID=@ProgramID WHERE ID=@ID";
            command.Parameters.Add(new SqlParameter("@ID", ID));
            command.Parameters.Add(new SqlParameter("@ProgramID", ProgramID));

            int res = DBHelper.ExecuteNonQuery(command);

            if (res == 1)
                return true;

            return false;
        }

        /// <summary>
        /// Gets stage description
        /// </summary>
        /// <param name="ID">Stage ID</param>
        /// <returns>stage description</returns>
        public static string GetDescription(int ID)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "Select Description FROM Stages WHERE ID=@ID";
            command.Parameters.Add(new SqlParameter("@ID", ID));
            return DBHelper.ExecuteScalar(command);
        }

        /// <summary>
        /// Gets stage ID and returns Owner ID of the stage's parent program
        /// </summary>
        /// <param name="ID">Stage ID</param>
        /// <returns>Owner ID of the stage's parent program</returns>
        public static int GetOwnerID(int ID)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "SELECT Programs.OwnerID FROM Stages JOIN Programs ON Programs.ID=Stages.ProgramID WHERE Stages.ID=@ID";
            command.Parameters.Add(new SqlParameter("@ID", ID));
            string res = DBHelper.ExecuteScalar(command);

            if (res != null && res.Length > 0)
            {
                return int.Parse(res);
            }

            return 0;
        }

        /// <summary>
        /// Gets Program ID and seperator, and returns String of all stages
        /// names on current program, seperated by seperator.
        /// </summary>
        /// <param name="ID">Program ID</param>
        /// <param name="separator">separator</param>
        /// <returns>String of all stages names on current program</returns>
        public static string EnumerateStages(int ID, string separator)
        {
            int ProgramID = GetProgramID(ID);
            string name = GetName(ID);

            StringBuilder res = new StringBuilder();

            SqlCommand command = new SqlCommand();
            command.CommandText = "Select Name FROM Stages WHERE ProgramID=@ProgramID";
            command.Parameters.Add(new SqlParameter("@ProgramID", ProgramID));
            DataTable dt = DBHelper.ExecuteQuery(command);

            bool isFirst = true;

            foreach (DataRow row in dt.Rows)
            {
                if (name != row[0].ToString())
                {
                    if (!isFirst)
                    {
                        res.Append(separator);
                    }
                    else
                    {
                        isFirst = false;
                    }
                    res.Append(row[0].ToString());
                    res.Append("$");
                }
            }

            return res.ToString();
        }

        /// <summary>
        /// Gets all stages names according to program ID that the stage belongs to.
        /// </summary>
        /// <param name="ProgramID">Program ID</param>
        /// <param name="separator">separator</param>
        /// <returns>String of all stages names on this program, seperate by seperator</returns>
        public static string EnumerateStagesForProgram(int ProgramID, string separator)
        {
            StringBuilder res = new StringBuilder();

            //Gets al stages names that belongs to this program ID
            SqlCommand command = new SqlCommand();
            command.CommandText = "Select Name FROM Stages WHERE ProgramID=@ProgramID";
            command.Parameters.Add(new SqlParameter("@ProgramID", ProgramID));
            DataTable dt = DBHelper.ExecuteQuery(command);

            bool isFirst = true;

            //Builds a string of all stages names
            foreach (DataRow row in dt.Rows)
            {
                if (!isFirst)
                {
                    res.Append(separator);
                }
                else
                {
                    isFirst = false;
                }
                res.Append(row[0].ToString());
                res.Append("$");
            }

            return res.ToString();
        }
    }
}